package pyx.crazycards.game.managers.persistence;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import pyx.crazycards.game.managers.annotation.DataObject;
import pyx.crazycards.game.managers.annotation.Propertie;

import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;

public class PersistenceUtil {

	public static String getDataObjectFullName(Object o) {
		DataObject dto = o.getClass().getAnnotation(DataObject.class);
		String ret = null;

		if (dto != null)
			ret = dto.base() != null ? !dto.base().isEmpty() ? dto.base() + "."
					+ dto.name() : dto.name() : dto.name();

		return ret;
	}

	public static String getDataObjectName(Object o) {
		DataObject dto = o.getClass().getAnnotation(DataObject.class);
		String ret = null;

		if (dto != null)
			ret = dto.name();

		return ret;
	}

	public static String getDataObjectBase(Object o) {
		DataObject dto = o.getClass().getAnnotation(DataObject.class);
		String ret = null;

		if (dto != null)
			ret = dto.base();

		return ret;
	}

	public static List<FieldValue> getFields(Object o)
			throws IllegalArgumentException, SecurityException,
			IllegalAccessException, NoSuchFieldException {

		Field fields[] = o.getClass().getDeclaredFields();
		ArrayList<FieldValue> ret = new ArrayList<FieldValue>();

		FieldValue val = null;

		for (Field fd : fields) {
			String name = getFieldName(fd);

			if (name != null) {
				val = new FieldValue();
				val.setName(name);
				val.setValue(getFieldValue(fd, o));
				val.setPersist(isPersistable(fd));
				val.setCollection(isDataObjectCollection(fd));
				val.setEnumeration(fd.getType().isEnum());
				ret.add(val);
			}
		}

		return ret;
	}

	private static boolean isPersistable(Field fd) {
		Propertie prop = fd.getAnnotation(Propertie.class);
		return prop.persist();
	}

	private static boolean isAbleLoad(Field fd) {
		Propertie prop = fd.getAnnotation(Propertie.class);
		return prop.load();
	}
	
	public static String getFieldName(Field field) {
		Propertie prop = field.getAnnotation(Propertie.class);
		String ret = null;

		if (prop != null)
			ret = prop.name();

		return ret;
	}

	public static Object getFieldValue(Field field, Object owner)
			throws IllegalArgumentException, SecurityException,
			IllegalAccessException, NoSuchFieldException {
		Object ret = null;

		Field fd = owner.getClass().getDeclaredField(field.getName());
		fd.setAccessible(true);

		ret = fd.get(owner);

		return ret;
	}

	@SuppressWarnings("all")
	public static Object fillFields(DBObject dbo, Object o)
			throws InstantiationException, IllegalAccessException,
			SecurityException, NoSuchMethodException, IllegalArgumentException,
			InvocationTargetException {

		Field fields[] = o.getClass().getDeclaredFields();

		if (dbo.get("_id") != null)
			((Persistable) o).set_id(dbo.get("_id").toString());

		for (Field fd : fields) {
			
			if(!isAbleLoad(fd))
				continue;
			
			String name = getFieldName(fd);
			String methodName = "set"
					+ fd.getName().substring(0, 1).toUpperCase()
					+ fd.getName().substring(1);

			Method setm = o.getClass().getDeclaredMethod(methodName,
					fd.getType());
			Object value = null;
			
			if(dbo.get(name) == null)
				continue;
			
			if (isDataObjectCollection(fd)) {
				value = fillCollection(fd, dbo.get(name));
			} else if (isHashMap(fd)) {
				value = fillHash(fd, (DBObject) dbo.get(name));
			} else if (isKnown(fd)) {
				value = dbo.get(name);
			} else if (fd.getType().isEnum()) {			 
				value = Enum.valueOf((Class)fd.getType(), dbo.get(name).toString());
			} else {
				value = fillFields((DBObject) dbo.get(name), fd.getType()
						.newInstance());
			}

			setm.invoke(o, value);
		}

		return o;
	}

	@SuppressWarnings("unchecked")
	private static HashMap fillHash(Field fd, DBObject data)
			throws InstantiationException, IllegalAccessException {

		HashMap map = (HashMap) fd.getType().newInstance();

		for (Object k : data.keySet()) {
			map.put(k, data.get(k.toString()));
		}

		return map;
	}

	private static boolean isHashMap(Field fd) {
		return HashMap.class.isAssignableFrom(fd.getType());
	}

	private static boolean isKnown(Field fd) {
		return fd.getType().isAssignableFrom(Long.class)
				|| fd.getType().isAssignableFrom(Integer.class)
				|| fd.getType().isAssignableFrom(Boolean.class)
				|| fd.getType().isAssignableFrom(Short.class)
				|| fd.getType().isAssignableFrom(Byte.class)
				|| fd.getType().isAssignableFrom(Character.class)
				|| fd.getType().isAssignableFrom(String.class)
				|| fd.getType().isAssignableFrom(Collection.class)
				|| fd.getType().isAssignableFrom(byte[].class);

	}

	public static DBObject getDBObject(Object o)
			throws IllegalArgumentException, SecurityException,
			IllegalAccessException, NoSuchFieldException {
		BasicDBObject ret = new BasicDBObject();

		List<FieldValue> fds = getFields(o);
		Object value;

		for (FieldValue fd : fds) {

			if (!fd.isPersist())
				continue;

			value = fd.getValue();

			if (value == null)
				continue;

			if(fd.isEnumeration())//ENUMERATION
				value = value.toString();
				
			if (isDataObject(value))//DATA OBJECT
				value = getDBObject(value);

			if (fd.isCollection())//COLLECTION
				value = parseCollection(value);

			ret.put(fd.getName(), value);
		}

		return ret;
	}

	private static boolean isDataObject(Object o) {
		DataObject dto = o.getClass().getAnnotation(DataObject.class);

		return dto != null;
	}

	private static boolean isDataObjectCollection(Field o) {
		Propertie dto = o.getAnnotation(Propertie.class);

		return dto != null && dto.dataObjectCollection();
	}

	@SuppressWarnings("all")
	public static Object fillCollection(Field fd, Object data)
			throws SecurityException, IllegalArgumentException,
			InstantiationException, IllegalAccessException,
			NoSuchMethodException, InvocationTargetException {

		Object ret = null;
		if (data != null) {

			if (fd.getType().isAssignableFrom(HashMap.class)) {
				HashMap list = new HashMap();
				DBObject source = (DBObject) data;

				ParameterizedType type = (ParameterizedType) fd
						.getGenericType();
				Class clazz = (Class) type.getActualTypeArguments()[1];

				for (Object k : source.keySet()) {

					Constructor constructor = clazz.getConstructor();
					constructor.setAccessible(true);

					list.put(k, fillFields((DBObject) source.get(k.toString()),
							constructor.newInstance()));
				}

				ret = list;
			} else if (fd.getType().isAssignableFrom(List.class)) {
				List list = new ArrayList();
				List source = (List) data;

				ParameterizedType type = (ParameterizedType) fd
						.getGenericType();
				Class clazz = (Class) type.getActualTypeArguments()[0];

				for (Object o : source) {

					list.add(fillFields((DBObject) o, clazz.newInstance()));
				}

				ret = list;
			}
		}
		return ret;
	}

	@SuppressWarnings("all")
	public static Object parseCollection(Object o)
			throws IllegalArgumentException, SecurityException,
			IllegalAccessException, NoSuchFieldException {
		Object ret = null;

		if (o != null) {
			if (o instanceof HashMap<?, ?>) {
				HashMap map = new HashMap();
				HashMap source = ((HashMap) o);

				for (Object key : source.keySet()) {
					map.put(key, getDBObject(source.get(key)));
				}

				ret = map;

			} else if (o instanceof List<?>) {
				List list = new ArrayList();

				for (Object obj : ((List) o)) {
					list.add(getDBObject(obj));
				}

				ret = list;
			}
		}
		return ret;
	}
}
